/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.core;

import java.util.*;
import org.latestbit.smartobjects.*;
import org.latestbit.smartobjects.network.*;

public class ServerInstancesRegistry extends TimerTask implements IServerInstancesRegistry, IConnectionListener {
		
	public static final int DEFAULT_TIMER_WAKE_UP_INTERVAL = 30; // 30 seconds
	
	private Set< IServerSkeleton > instances = new HashSet < IServerSkeleton > ();
	
	private Map< IConnection,  Set<IServerSkeleton> > instancesPerConnection = new HashMap <  IConnection, Set < IServerSkeleton > > ();
	private Timer registryTimer = new Timer( "ServerInstancesRegistryTimer", true );
	private int timerWakeUpInterval = DEFAULT_TIMER_WAKE_UP_INTERVAL;
	
	Collection<IServerSkeleton> toDestroyTempStorage = new LinkedList<IServerSkeleton> ();

	private boolean hasInstancesByTime = false;
	
	public ServerInstancesRegistry() {
		
	}

	@Override
	public void addInstance ( IServerSkeleton instance ) {
		LifecycleControlParams ltcp = instance.getLifecycleControlParams();
		if(!ltcp.isDestroyByLastActivityTime() && !ltcp.isDestroyByRemoteConnectionClosed())
			throw new IllegalArgumentException("Invalid life time control params. At least one of controls params must be selected!");
		synchronized(instances) {
			instances.add (instance);
			if(ltcp.isDestroyByLastActivityTime() && !hasInstancesByTime) {
				hasInstancesByTime = true;
				startIntervalTimerNotification();
			}
		}		
	}

	@Override
	public void attachInstanceClientHandler ( ConnectionHandler handler, IServerSkeleton instance ) {
		synchronized ( instances ) {
			if(!instances.contains(instance))
				throw new IllegalArgumentException("Unable to attache instance to connection handler. The specified instance is not found in the registry!");
			IConnection connection = handler.getConnection();
			if(connection!=null) {
			
				Set<IServerSkeleton> instancesByConnection = instancesPerConnection.get(connection);
	
				if(instancesByConnection == null) {
					connection.addConnectionListener(this);
					instancesByConnection = new HashSet<IServerSkeleton>();
					instancesPerConnection.put(connection, instancesByConnection);
				}
				instancesByConnection.add(instance);
			}
		}
	}

	@Override
	public void detachInstanceClientHandler ( ConnectionHandler handler, IServerSkeleton instance ) {
		IConnection connection = handler.getConnection();
		boolean isNeedToRemoveListener = false;
		synchronized ( instances ) {
			if(instances.contains(instance))
				throw new IllegalArgumentException("Unable to detach instance from connection handler. The specified instance is not found in the registry!");
			
			if(connection!=null) {
				Set<IServerSkeleton> instancesByConnection = instancesPerConnection.get(connection);
				if(instancesByConnection!=null) {
					instancesByConnection.remove(instance);
					if(instancesByConnection.isEmpty()) {
						isNeedToRemoveListener = true;						
					}
				}
				instancesPerConnection.remove ( connection );
			}
		}
		
		if(isNeedToRemoveListener) {
			connection.delConnectionListener(this);	
		}
	}

	@Override
	public void removeInstance ( IServerSkeleton instance ) {
		Collection<IConnection> toDestroyTempConnectionStorage = new LinkedList<IConnection> ();
		synchronized ( instances ) {
			instances.remove(instance);			
			
			// Remove instance from all connections
			Iterator< Map.Entry< IConnection, Set<IServerSkeleton> > > it = instancesPerConnection.entrySet().iterator();
			while(it.hasNext()) {
				Map.Entry< IConnection , Set<IServerSkeleton> > entry = it.next();
				if(entry.getValue().remove(instance)) {
					if(entry.getValue().isEmpty()) {
						toDestroyTempConnectionStorage.add(entry.getKey());												
						it.remove();
						it = instancesPerConnection.entrySet().iterator();
					}
				}
			}
		}
		
		for(IConnection connection : toDestroyTempConnectionStorage) {
			connection.delConnectionListener(this);
		}
	}

	
	private synchronized void startIntervalTimerNotification() {
		if( hasInstancesByTime ) {
			this.registryTimer.schedule(this, getTimerWakeUpInterval()*1000, getTimerWakeUpInterval()*1000 );
		}
	}

	private synchronized void stopIntervalTimerNotification() {
		// this.registryTimer.
	}
	
	@Override
	public void onConnected(IConnection connection) {}

	@Override
	public void onConnectionFailure(IConnection connection, String errorMessage) {}

	@Override
	public void onDisconnected(IConnection connection) {
		synchronized ( instances ) {
			Set< IServerSkeleton > ic = instancesPerConnection.get(connection);
			if(ic!=null) {
				instancesPerConnection.remove(connection);
				for(IServerSkeleton instance : ic) {
					destroyInstanceOnDisconnect ( instance );
				}
			}
		}				
	}
	
	protected void destroyInstanceOnDisconnect(IServerSkeleton instance) {
		if(instance.getLifecycleControlParams().isDestroyByRemoteConnectionClosed()) {
			instance.destroy();
			instances.remove(instance);
		}
	}	

	@Override
	public synchronized void setTimerWakeUpInterval(int timerWakeUpInterval) {
		this.timerWakeUpInterval = timerWakeUpInterval;
		stopIntervalTimerNotification();
		startIntervalTimerNotification();
	}

	@Override
	public int getTimerWakeUpInterval() {
		return timerWakeUpInterval;
	}
	
	@Override
	public void run() {
		synchronized(toDestroyTempStorage) {
			toDestroyTempStorage.clear();
			synchronized ( instances ) {
				long currentTime = new Date().getTime();			
				for ( IServerSkeleton instance : instances ) {
					if ( instance.getLifecycleControlParams().isDestroyByLastActivityTime() ) {
						long lastActivityTime = instance.getLastActivityTime().getTime();
						if(currentTime - lastActivityTime > instance.getLifecycleControlParams().getMaxLastActivityAwaitTime()*1000) {
							toDestroyTempStorage.add(instance);
						}
					}
				}
				if(instances.isEmpty()) {
					hasInstancesByTime = false;
					stopIntervalTimerNotification();
				}
			}
	
			for ( IServerSkeleton instance : toDestroyTempStorage ) {
				instance.destroy();
				removeInstance(instance);
			}
		}
	}

	@Override
	public int getInstancesCount() {
		synchronized(instances) {
			return instances.size();
		}
	}

	@Override
	public void destroy() {
		stopIntervalTimerNotification();
		this.cancel();		
		Set< IServerSkeleton > instancesCopy = new HashSet <IServerSkeleton>();
		synchronized ( instances ) {			
			instancesCopy.addAll(instances);
			instances.clear();			
		}
		
		for ( IServerSkeleton instance : instancesCopy ) {
			instance.destroy();
		}		
	}

}
